﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO.Ports;
using System.Threading;
using System.IO;

namespace RoboServ
{
    class XBeeSerial : SerialPort
    {
        public const byte API_PACKET_START = 0x7E;

        private Object lockObject = new Object();

        private Thread readThread;
        private bool isOpen = false;

        private List<XBeeResponse> respList = new List<XBeeResponse>();

        public XBeeSerial() : base()
        {
            
        }
        
        public void open(String portName, int baudRate)
        {
            try
            {
                this.PortName = portName;
                this.BaudRate = baudRate;
                this.Open();
                isOpen = true;

                readThread = new Thread(read);
                readThread.Start();

                RoboServForm.log("Port " + portName + " opened.");

                sendLocalMessage(XBeeRequest.REQ_AT_LOCAL, 0x40, "BD");

            }
            catch (IOException ex)
            {
                RoboServForm.log("Port " + portName + " failed to open:");
                RoboServForm.log(ex.StackTrace);
            }
        }

        public void close()
        {
            isOpen = false;
            readThread.Join();
            this.Close();
            readThread = null;
            respList.Clear();
            RoboServForm.log("Port " + this.PortName + " closed.");
        }

        public bool hasBytes()
        {
            return (this.BytesToRead > 0);
        }

        private void read()
        {
            while (isOpen)
            {
                try
                {
                    while (hasBytes())
                    {
                        byte b = readByte();
                        if (b == API_PACKET_START)
                        {
                            byte[] lenBytes = new byte[2];

                            lenBytes[0] = readByte();
                            lenBytes[1] = readByte();

                            int len = (int)(Utils.bytesToInt(lenBytes) + 1);

                            byte[] data = new byte[len];

                            Read(data, 0, len);

                            XBeeResponse xbr = new XBeeResponse(data);

                            if (xbr.checksumFailed)
                            {
                                RoboServForm.log("Recv: " + Utils.byteArrayToString(data) + " (checksum failed)");
                            }
                            else
                            {
                                RoboServForm.log("Recv: " + Utils.byteArrayToString(data));
                            }

                            // Thread-safe modification of respList? Hopefully?
                            lock (lockObject)
                            {
                                respList.Add(xbr);
                            }
                        }
                        
                    }
                }
                catch (TimeoutException) { }
            }
        }

        public XBeeResponse getResponse()
        {
            if (respList.Count > 0)
            {
                XBeeResponse ret = respList[0];

                // Thread-safe modification of respList? Hopefully?
                lock (lockObject)
                {
                    respList.RemoveAt(0);
                }

                return ret;
            }
            else
            {
                return null;
            }
        }

        public byte readByte()
        {
            return (byte)ReadByte();
        }

        public void sendRX(byte msgID, UInt16 destAddr, byte[] msg)
        {
            sendRemoteMessage(XBeeRequest.REQ_TX_16BIT, msgID, destAddr, msg);
        }

        public void sendRemoteMessage(byte apiID, byte msgID, UInt16 destAddr, String msg)
        {
            sendRemoteMessage(apiID, msgID, destAddr, Encoding.ASCII.GetBytes(msg));
        }

        public void sendRemoteMessage(byte apiID, byte msgID, UInt16 destAddr, byte[] msg)
        {
            XBeeRequest xbm = new XBeeRequest(apiID, msgID, msg);

            xbm.set16BitAddress(destAddr);

            byte[] req = xbm.buildRequest();

            Write(req, 0, req.Length);

            RoboServForm.log("Sent: {0}", Utils.byteArrayToString(req));
        }

        public void sendLocalMessage(byte apiID, byte msgID, String msg)
        {
            sendLocalMessage(apiID, msgID, Encoding.ASCII.GetBytes (msg));
        }

        public void sendLocalMessage(byte apiID, byte msgID, byte[] msg)
        {
            XBeeRequest xbm = new XBeeRequest(apiID, msgID, msg);

            byte[] req = xbm.buildRequest();

            Write(req, 0, req.Length);

            RoboServForm.log("Sent: {0}", Utils.byteArrayToString(req));

        }

    }
}
